/*
 * Copyright (C) 2016 Jared Rummler
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

package it.bradipao.deschartsdemo.spinner;

import ohos.aafwk.ability.Ability;
import ohos.agp.animation.Animator;
import ohos.agp.animation.AnimatorProperty;
import ohos.agp.colors.RgbColor;
import ohos.agp.components.AttrSet;
import ohos.agp.components.Component;
import ohos.agp.components.ComponentContainer;
import ohos.agp.components.ComponentParent;
import ohos.agp.components.DependentLayout;
import ohos.agp.components.DirectionalLayout;
import ohos.agp.components.Image;
import ohos.agp.components.ListContainer;
import ohos.agp.components.RecycleItemProvider;
import ohos.agp.components.Text;
import ohos.agp.components.element.PixelMapElement;
import ohos.agp.components.element.ShapeElement;
import ohos.agp.components.element.StateElement;
import ohos.agp.render.BlendMode;
import ohos.agp.render.ColorMatrix;
import ohos.agp.utils.Color;
import ohos.agp.utils.LayoutAlignment;
import ohos.agp.utils.TextAlignment;
import ohos.agp.utils.TextTool;
import ohos.agp.window.dialog.BaseDialog;
import ohos.agp.window.dialog.IDialog;
import ohos.agp.window.dialog.PopupDialog;
import ohos.app.AbilityContext;
import ohos.app.Context;
import ohos.global.resource.NotExistException;
import ohos.global.resource.ResourceManager;
import ohos.global.resource.WrongTypeException;
import ohos.hiviewdfx.HiLog;
import ohos.hiviewdfx.HiLogLabel;
import ohos.multimodalinput.event.MmiPoint;
import ohos.multimodalinput.event.TouchEvent;

import java.io.IOException;
import java.util.Arrays;
import java.util.List;

import it.bradipao.deschartsdemo.ResourceTable;
import it.bradipao.deschartsdemo.spinner.util.PixelMapUtils;
import it.bradipao.deschartsdemo.spinner.util.TypedAttrUtils;
import it.bradipao.deschartsdemo.spinner.util.Utils;

import static ohos.agp.components.Text.TruncationMode.ELLIPSIS_AT_END;

/**
 * A spinner that shows a {@link PopupDialog} under the view when clicked.
 *
 * @since 2021-07-30
 */
public class MaterialSpinner extends DependentLayout implements Component.TouchEventListener, Component.ClickedListener {
    private static final HiLogLabel LABEL = new HiLogLabel(HiLog.LOG_APP, 0x00201, "MaterialSpinner");
    private static final int DEF_VALUE = 1080;
    private static final int SPINNER_IMG_ID = 100;
    private static final int SPINNER_MARGIN_RIGHT = 30;
    private static final int SPINNER_TEXT_SIZE = 42;
    private static final int ANGLE = 180;
    private static final int DURATION = 390;
    private static final int SCROLLBAR_THICKNESS = 15;
    private static final int SCROLLBAR_RADIUS = 10;
    private static final int CORNER_RADIUS = 20;
    private static final int STROKE_WIDTH = 3;
    private OnNothingSelectedListener onNothingSelectedListener;
    private OnItemSelectedListener onItemSelectedListener;
    private MaterialSpinnerBaseAdapter adapter;
    private PopupDialog popupWindow;
    private ListContainer listView;
    private PixelMapElement arrowDrawable;
    private boolean hideArrow;
    private boolean nothingSelected;
    private int spinnerWidth;
    private int popupWindowMaxHeight;
    private int popupWindowHeight;
    private int selectedIndex;
    private int backgroundColor;
    private StateElement backgroundSelector;
    private int arrowColor;
    private int arrowColorDisabled;
    private int textColor;
    private int textSize;
    private int hintColor;
    private int popupPaddingTop;
    private int popupPaddingLeft;
    private int popupPaddingBottom;
    private int popupPaddingRight;
    private String hintText;
    private MaterialSpinnerBaseAdapter mAdapter;
    private Text spinnerTxt;
    private Image spinnerImg;
    private boolean isRtl;
    private boolean isShowBottom;
    private long startTime = 0;

    /**
     * MaterialSpinner
     *
     * @param context context
     */
    public MaterialSpinner(Context context) {
        super(context);
        init(context, null);
    }

    /**
     * MaterialSpinner
     *
     * @param context context
     * @param attrs attrs
     */
    public MaterialSpinner(Context context, AttrSet attrs) {
        super(context, attrs);
        init(context, attrs);
    }

    /**
     * MaterialSpinner
     *
     * @param context context
     * @param attrs attrs
     * @param styleName styleName
     */
    public MaterialSpinner(Context context, AttrSet attrs, String styleName) {
        super(context, attrs, styleName);
        init(context, attrs);
    }

    private void init(Context context, AttrSet attrs) {
        composeComponent(context);
        Color defaultColor = spinnerTxt.getTextColor();
        isRtl = Utils.isRtl(context);

        int paddingLeft = 0, paddingTop = 0, paddingRight = 0, paddingBottom = 0;
        int defaultPaddingLeft, defaultPaddingTop, defaultPaddingRight, defaultPaddingBottom;
        int defaultPopupPaddingLeft, defaultPopupPaddingTop, defaultPopupPaddingRight, defaultPopupPaddingBottom;
        spinnerWidth = this.getWidth();

        ResourceManager resourceManager = getResourceManager();
        try {
            defaultPaddingLeft = defaultPaddingRight =
                defaultPaddingBottom = defaultPaddingTop = (int) resourceManager.getElement(ResourceTable.Float_ms_padding_top).getFloat();
            if (isRtl) {
                defaultPaddingRight = (int) resourceManager.getElement(ResourceTable.Float_ms_padding_left).getFloat();
            } else {
                defaultPaddingLeft = (int) resourceManager.getElement(ResourceTable.Float_ms_padding_left).getFloat();
            }
            defaultPopupPaddingLeft =
                defaultPopupPaddingRight = (int) resourceManager.getElement(ResourceTable.Float_ms_padding_left).getFloat();
            defaultPopupPaddingTop =
                defaultPopupPaddingBottom = (int) resourceManager.getElement(ResourceTable.Float_ms_padding_top).getFloat();

            backgroundColor = TypedAttrUtils.getIntColor(attrs, "ms_background_color", Color.WHITE.getValue());
            backgroundSelector = (StateElement) TypedAttrUtils.getLayoutDimension(attrs, "ms_background_selector", null);
            textColor = TypedAttrUtils.getIntColor(attrs, "ms_text_color", defaultColor.getValue());
            hintColor = TypedAttrUtils.getIntColor(attrs, "ms_hint_color", defaultColor.getValue());
            arrowColor = TypedAttrUtils.getIntColor(attrs, "ms_arrow_tint", textColor);
            hideArrow = TypedAttrUtils.getBoolean(attrs, "ms_hide_arrow", false);
            hintText = TypedAttrUtils.getString(attrs, "ms_hint", "");

            isShowBottom = TypedAttrUtils.getBoolean(attrs, "ms_is_show_bottom", false);


            popupWindowMaxHeight = TypedAttrUtils.getDimensionPixelSize(attrs, "ms_dropdown_max_height", DEF_VALUE);
            popupWindowHeight = TypedAttrUtils.getDimensionPixelSize(attrs, "ms_dropdown_height",
                ComponentContainer.LayoutConfig.MATCH_CONTENT);
            paddingTop = TypedAttrUtils.getDimensionPixelSize(attrs, "ms_padding_top", defaultPaddingTop);
            paddingLeft = TypedAttrUtils.getDimensionPixelSize(attrs, "ms_padding_left", defaultPaddingLeft);
            paddingBottom = TypedAttrUtils.getDimensionPixelSize(attrs, "ms_padding_bottom", defaultPaddingBottom);
            paddingRight = TypedAttrUtils.getDimensionPixelSize(attrs, "ms_padding_right", defaultPaddingRight);
            popupPaddingTop = TypedAttrUtils.getDimensionPixelSize(attrs, "ms_popup_padding_top", defaultPopupPaddingTop);
            popupPaddingLeft = TypedAttrUtils.getDimensionPixelSize(attrs, "ms_popup_padding_left", defaultPopupPaddingLeft);
            popupPaddingBottom = TypedAttrUtils.getDimensionPixelSize(attrs, "ms_popup_padding_bottom", defaultPopupPaddingBottom);
            popupPaddingRight = TypedAttrUtils.getDimensionPixelSize(attrs, "ms_popup_padding_right", defaultPopupPaddingRight);
            arrowColorDisabled = Utils.lighter(arrowColor, 0.8f);
            HiLog.info(LABEL, "init textColor = " + textColor
                + ", popupWindowMaxHeight = " + popupWindowMaxHeight
                + ", arrowColorDisabled = " + arrowColorDisabled
            );
        } catch (IOException e) {
            HiLog.info(LABEL, "IOException = " + e.toString());
        } catch (NotExistException e) {
            HiLog.info(LABEL, "NotExistException = " + e.toString());
        } catch (WrongTypeException e) {
            HiLog.info(LABEL, "WrongTypeException = " + e.toString());
        } catch (Exception e) {
            HiLog.info(LABEL, "Exception = " + e.toString());
        }
        nothingSelected = true;
        setClickable(true);
        spinnerTxt.setTextAlignment(TextAlignment.VERTICAL_CENTER | TextAlignment.START);
        spinnerTxt.setMarginsLeftAndRight(paddingLeft, paddingRight);
        spinnerTxt.setMarginsTopAndBottom(paddingTop, paddingBottom);
        if (isRtl) {
            setLayoutDirection(LayoutDirection.RTL);
        }
        if (!hideArrow) {
            arrowDrawable = PixelMapUtils.getPixelMapElement(context, ResourceTable.Media_ic_arrow);
            getArrowDrawable(arrowColor, BlendMode.SRC_IN);
            spinnerImg.setImageElement(arrowDrawable);
        }

        if (textColor != defaultColor.getValue()) {
            setTextColor(new Color(textColor));
        }
        textSize = spinnerTxt.getTextSize();

        initPopupWindow(context);
        setClickedListener(this);
    }

    private void initPopupWindow(Context context) {
        listView = new ListContainer(context);
        DirectionalLayout.LayoutConfig layoutConfig =
            new DirectionalLayout.LayoutConfig(LayoutConfig.MATCH_PARENT, LayoutConfig.MATCH_CONTENT);
        listView.setLayoutConfig(layoutConfig);
        listView.setId(getId());
        listView.setScrollbarColor(Color.GRAY);
        listView.setScrollbarThickness(SCROLLBAR_THICKNESS);
        listView.setLongClickable(false);
        listView.enableScrollBar(AXIS_Y, true);
        listView.setScrollbarRoundRect(true);
        listView.setScrollbarFadingEnabled(true);
        listView.setScrollbarRadius(SCROLLBAR_RADIUS);
        ShapeElement background = new ShapeElement(context, ResourceTable.Graphic_ms__drawable);
        background.setCornerRadius(CORNER_RADIUS);
        background.setStroke(STROKE_WIDTH, RgbColor.fromArgbInt(Color.getIntColor("#9496A3")));
        listView.setBackground(background);
        listView.setItemProvider(mAdapter);
        listView.scrollTo(selectedIndex);
        listView.setItemClickedListener(new ListContainer.ItemClickedListener() {
            @Override
            public void onItemClicked(ListContainer parent, Component view, int position, long id) {
                selectedIndex = position;
                nothingSelected = false;
                Object item = adapter.get(position);
                adapter.notifyItemSelected(position);
                spinnerTxt.setTextColor(new Color(MaterialSpinner.this.textColor));
                spinnerTxt.setText(item.toString());
                spinnerTxt.setMultipleLine(true);
                collapse();
                if (onItemSelectedListener != null) {
                    onItemSelectedListener.onItemSelected(MaterialSpinner.this, position, id, item);
                }
            }
        });

        popupWindow = new PopupDialog(context, this);

        popupWindow.setCustomComponent(listView);

        HiLog.info(LABEL, "backgroundColor = " + (backgroundColor != Color.WHITE.getValue()));
        if (backgroundColor != Color.WHITE.getValue()) { // default color is white
            ShapeElement element = new ShapeElement();
            element.setRgbColor(RgbColor.fromArgbInt(backgroundColor));
            setBackground(element);
            listView.setBackground(element);
        } else {
            setBackground(backgroundSelector);
        }
        setPopupListener();
    }

    private void setPopupListener() {
        popupWindow.setDialogListener(new BaseDialog.DialogListener() {
            @Override
            public boolean isTouchOutside() {
                HiLog.info(LABEL, "setDialogListener isTouchOutside " + nothingSelected
                    + ", " + onNothingSelectedListener);
                if (popupWindow != null) {
                    popupWindow.destroy();
                    popupWindow = null;
                }
                if (nothingSelected && onNothingSelectedListener != null) {
                    onNothingSelectedListener.onNothingSelected(MaterialSpinner.this);
                }
                if (!hideArrow) {
                    animateArrow(false);
                }
                return true;
            }
        });
        popupWindow.registerDisplayCallback(new PopupDialog.DisplayCallback() {
            @Override
            public void onDisplay(IDialog dialog) {
                HiLog.info(LABEL, "registerDisplayCallback iDialog " + dialog);
            }
        });
        popupWindow.registerRemoveCallback(new BaseDialog.RemoveCallback() {
            @Override
            public void onRemove(IDialog dialog) {
                HiLog.info(LABEL, "registerRemoveCallback iDialog " + dialog);
            }
        });
    }

    private void composeComponent(Context context) {
        spinnerImg = new Image(context);
        spinnerImg.setId(SPINNER_IMG_ID);
        LayoutConfig layoutConfigImg =
            new LayoutConfig(LayoutConfig.MATCH_CONTENT, LayoutConfig.MATCH_CONTENT);
        layoutConfigImg.addRule(LayoutConfig.ALIGN_PARENT_RIGHT);
        layoutConfigImg.addRule(LayoutConfig.VERTICAL_CENTER);
        spinnerImg.setLayoutConfig(layoutConfigImg);
        spinnerImg.setScaleMode(Image.ScaleMode.INSIDE);
        spinnerImg.setMarginRight(SPINNER_MARGIN_RIGHT);
        this.addComponent(spinnerImg);

        spinnerTxt = new Text(context);
        LayoutConfig layoutConfigTxt =
            new LayoutConfig(LayoutConfig.MATCH_PARENT, LayoutConfig.MATCH_CONTENT);
        layoutConfigTxt.addRule(LayoutConfig.LEFT_OF, SPINNER_IMG_ID);
        spinnerTxt.setLayoutConfig(layoutConfigTxt);
        this.addComponent(spinnerTxt);
        spinnerTxt.setTextSize(SPINNER_TEXT_SIZE);
        spinnerTxt.setMaxTextLines(1);
        spinnerTxt.setTruncationMode(ELLIPSIS_AT_END);
    }

    @Override
    public void onClick(Component component) {
        long currentTime = System.currentTimeMillis();
        if (currentTime - startTime < 300) {
            return;
        }
        if (popupWindow != null && popupWindow.isShowing()) {
            popupWindow.hide();
        }
        startTime = System.currentTimeMillis();
        expand();
    }

    @Override
    public boolean onTouchEvent(Component component, TouchEvent touchEvent) {
        HiLog.info(LABEL, "onTouchEvent = " + touchEvent.getAction());
        MmiPoint pointerPosition = touchEvent.getPointerPosition(0);
        int x = (int) pointerPosition.getX();
        int y = (int) pointerPosition.getY();
        HiLog.info(LABEL, "x = " + x + ", y = " + y);
        long currentTime = System.currentTimeMillis();
        if (currentTime - startTime < 200) {
            return false;
        }
        switch (touchEvent.getAction()) {
            case TouchEvent.PRIMARY_POINT_DOWN:
                return true;
            case TouchEvent.PRIMARY_POINT_UP:
                startTime = System.currentTimeMillis();
                HiLog.info(LABEL, "onTouchEvent = " + "isEnabled = " + isEnabled()
                    + ", isClickable = " + isClickable());
                if (isEnabled() && isClickable()) {
                    expand();
                }
                return false;
            default:
                break;
        }
        return false;
    }

    /**
     * setTextSize
     *
     * @param size size
     */
    public void setTextSize(int size) {
        HiLog.info(LABEL, "setTextSize adapter = " + adapter + ", textSize = " + textSize);
        if (adapter != null) {
            adapter.setTextSize(textSize);
            adapter.notifyDataChanged();
        }
        spinnerTxt.setTextSize(size);
    }

    /**
     * setTextColor
     *
     * @param color color
     */
    public void setTextColor(Color color) {
        HiLog.info(LABEL, "@Override setTextColor adapter = " + adapter);
        textColor = color.getValue();
        if (adapter != null) {
            adapter.setTextColor(textColor);
            adapter.notifyDataChanged();
        }
        spinnerTxt.setTextColor(color);
    }

    /**
     * setHintColor
     *
     * @param color color
     */
    public void setHintColor(Color color) {
        hintColor = color.getValue();
        spinnerTxt.setTextColor(color);
    }

    @Override
    public void setEnabled(boolean enabled) {
        super.setEnabled(enabled);
        if (arrowDrawable != null) {
            getArrowDrawable(enabled ? arrowColor : arrowColorDisabled, BlendMode.SRC_IN);
        }
    }

    /**
     * get the selectedIndex
     *
     * @return the selected item position
     */
    public int getSelectedIndex() {
        return selectedIndex;
    }

    /**
     * Set the default spinner item using its index
     *
     * @param position the item's position
     */
    public void setSelectedIndex(int position) {
        if (adapter != null) {
            if (position >= 0 && position <= adapter.getCount()) {
                adapter.notifyItemSelected(position);
                selectedIndex = position;
                spinnerTxt.setText(adapter.get(position).toString());
            } else {
                throw new IllegalArgumentException("Position must be lower than adapter count!");
            }
        }
    }

    /**
     * Register a callback to be invoked when an item in the dropdown is selected.
     *
     * @param onItemSelectedListener The callback that will run
     */
    public void setOnItemSelectedListener(OnItemSelectedListener onItemSelectedListener) {
        this.onItemSelectedListener = onItemSelectedListener;
    }

    /**
     * Register a callback to be invoked when the {@link PopupDialog} is shown but the user didn't select an item.
     *
     * @param onNothingSelectedListener the callback that will run
     */
    public void setOnNothingSelectedListener(OnNothingSelectedListener onNothingSelectedListener) {
        this.onNothingSelectedListener = onNothingSelectedListener;
    }

    /**
     * Set the dropdown items
     *
     * @param items A list of items
     * @param <T> The item type
     */
    public <T> void setItems(T... items) {
        setItems(Arrays.asList(items));
    }

    /**
     * Set the dropdown items
     *
     * @param items A list of items
     * @param <T> The item type
     */
    public <T> void setItems(List<T> items) {
        adapter = new MaterialSpinnerAdapter<>(getContext(), items)
            .setPopupPadding(popupPaddingLeft, popupPaddingTop, popupPaddingRight, popupPaddingBottom)
            .setBackgroundSelector(backgroundSelector)
            .setTextSize(textSize)
            .setTextColor(textColor);
        setAdapterInternal(adapter);
    }

    /**
     * Set a custom adapter for the dropdown items
     *
     * @param adapter The list adapter
     */
    public void setAdapter(RecycleItemProvider adapter) {
        this.adapter = new MaterialSpinnerAdapterWrapper(getContext(), adapter)
            .setPopupPadding(popupPaddingLeft, popupPaddingTop, popupPaddingRight, popupPaddingBottom)
            .setBackgroundSelector(backgroundSelector)
            .setTextColor(textColor);
        setAdapterInternal(this.adapter);
    }

    /**
     * Set the custom adapter for the dropdown items
     *
     * @param adapter The adapter
     * @param <T> The type
     */
    public <T> void setAdapter(MaterialSpinnerAdapter<T> adapter) {
        this.adapter = adapter;
        this.adapter.setTextColor(textColor);
        this.adapter.setBackgroundSelector(backgroundSelector);
        this.adapter.setPopupPadding(popupPaddingLeft, popupPaddingTop, popupPaddingRight, popupPaddingBottom);
        setAdapterInternal(adapter);
    }

    private void setAdapterInternal(MaterialSpinnerBaseAdapter adapter) {
        this.mAdapter = adapter;
        boolean shouldResetPopupHeight = listView.getItemProvider() != null;
        adapter.setHintEnabled(!TextTool.isNullOrEmpty(hintText));
        listView.setItemProvider(adapter);
        HiLog.info(LABEL, "selectedIndex = " + selectedIndex + ", shouldResetPopupHeight = " + shouldResetPopupHeight);
        if (selectedIndex >= adapter.getCount()) {
            selectedIndex = 0;
        }
        if (adapter.getItems().size() > 0) {
            if (nothingSelected && !TextTool.isNullOrEmpty(hintText)) {
                spinnerTxt.setText(hintText);
                setHintColor(new Color(hintColor));
            } else {
                setTextColor(new Color(textColor));
                spinnerTxt.setText(adapter.get(selectedIndex).toString());
            }
        } else {
            spinnerTxt.setText("");
        }
        if (shouldResetPopupHeight) {
            popupWindow.setSize(getWidth(), calculatePopupWindowHeight());
        }
    }

    /**
     * Get the list of items in the adapter
     *
     * @param <T> The item type
     * @return A list of items or {@code null} if no items are set.
     */
    public <T> List<T> getItems() {
        if (adapter == null) {
            return null;
        }
        return adapter.getItems();
    }

    /**
     * Show the dropdown menu
     */
    public void expand() {
        HiLog.info(LABEL, "expand = ");
        if (!hideArrow) {
            animateArrow(true);
        }
        nothingSelected = true;
        initPopupWindow(mContext);
        popupWindow.setSize(getWidth(), calculatePopupWindowHeight());
        int[] locationOnScreen = this.getLocationOnScreen();
        HiLog.info(LABEL, "locationOnScreen[1] = " + locationOnScreen[1] + ", " + spinnerWidth);
        if (isShowBottom) {
            popupWindow.showOnCertainPosition(LayoutAlignment.LEFT | LayoutAlignment.TOP,
                locationOnScreen[0], locationOnScreen[1] + this.getHeight() + 0);
        } else {
            popupWindow.show();
        }
    }

    /**
     * Closes the dropdown menu
     */
    public void collapse() {
        if (!hideArrow) {
            animateArrow(false);
        }
        if (popupWindow != null) {
            popupWindow.destroy();
        }
        if (listView != null) {
            ComponentParent componentParent = listView.getComponentParent();
            if (componentParent != null) {
                componentParent.removeComponent(listView);
            }
        }
    }

    /**
     * color the color value
     *
     * @param color 色值
     */
    public void setArrowColor(int color) {
        arrowColor = color;
        arrowColorDisabled = Utils.lighter(arrowColor, 0.8f);
        if (arrowDrawable != null) {
            getArrowDrawable(arrowColor, BlendMode.SRC_IN);
        }
    }

    private boolean canShowPopup() {
        Ability activity = getActivity();
        if (activity == null || activity.isTerminating()) {
            return false;
        }
        boolean isLaidOut;
        isLaidOut = getWidth() > 0 && getHeight() > 0;
        return isLaidOut;
    }

    private Ability getActivity() {
        Context context = getContext();
        HiLog.info(LABEL, "activity = " + context + ", " + (context instanceof AbilityContext));
        while (context instanceof AbilityContext) {
            HiLog.info(LABEL, "(context instanceof Ability)" + (context instanceof Ability));
            if (context instanceof Ability) {
                return (Ability) context;
            }
            context = ((AbilityContext) context).getContext();
        }
        return null;
    }

    private void animateArrow(boolean shouldRotateUp) {
        if (isRtl) {
            setLayoutDirection(LayoutDirection.RTL);
        }
        AnimatorProperty animator = spinnerImg.createAnimatorProperty();
        animator.setCurveType(Animator.CurveType.SMOOTH_STEP);
        animator.rotate(shouldRotateUp ? ANGLE : 0).setDuration(DURATION).setLoopedCount(0);
        animator.start();
    }

    private void getArrowDrawable(int arrowColor, BlendMode blendMode) {
        int red = Utils.redInt(arrowColor);
        int green = Utils.greenInt(arrowColor);
        int blue = Utils.blueInt(arrowColor);
        int alpha = Color.alpha(arrowColor);
        ColorMatrix matrix = new ColorMatrix();
        HiLog.info(LABEL, "red = " + red
            + ", green = " + green
            + ", blue = " + blue
            + ", alpha = " + alpha
        );
        arrowDrawable.setColorMatrix(matrix);
        arrowDrawable.setStateColorMode(blendMode);
    }

    /**
     * Set the maximum height of the dropdown menu.
     *
     * @param height the height in pixels
     */
    public void setDropdownMaxHeight(int height) {
        popupWindowMaxHeight = height;
    }

    /**
     * Set the height of the dropdown menu
     *
     * @param height the height in pixels
     */
    public void setDropdownHeight(int height) {
        popupWindowHeight = height;
    }

    private int calculatePopupWindowHeight() {
        HiLog.info(LABEL, "calculatePopupWindowHeight adapter = " + adapter);
        if (adapter == null) {
            return ComponentContainer.LayoutConfig.MATCH_CONTENT;
        }
        float itemHeight = 0;
        try {
            itemHeight = this.getResourceManager().getElement(ResourceTable.Float_ms_item_height).getFloat();
        } catch (IOException e) {
            HiLog.info(LABEL, "exception = " + e.toString());
        } catch (NotExistException e) {
            HiLog.info(LABEL, "exception = " + e.toString());
        } catch (WrongTypeException e) {
            HiLog.info(LABEL, "exception = " + e.toString());
        }
        float listViewHeight = adapter.getCount() * itemHeight;
        HiLog.info(LABEL, "itemHeight = " + itemHeight + ", listViewHeight = " + listViewHeight
            + ", popupWindowMaxHeight = " + popupWindowMaxHeight + ", popupWindowHeight = " + popupWindowHeight);
        if (popupWindowMaxHeight > 0 && listViewHeight > popupWindowMaxHeight) {
            HiLog.info(LABEL, "if = ");
            return popupWindowMaxHeight;
        } else if (popupWindowHeight != ComponentContainer.LayoutConfig.MATCH_PARENT
            && popupWindowHeight != ComponentContainer.LayoutConfig.MATCH_CONTENT
            && popupWindowHeight <= listViewHeight) {
            HiLog.info(LABEL, "else if = ");
            return popupWindowHeight;
        } else if (listViewHeight == 0 && adapter.getItems().size() == 1) {
            HiLog.info(LABEL, "size() == 1 ");
            return (int) itemHeight;
        } else if (popupWindowHeight == ComponentContainer.LayoutConfig.MATCH_CONTENT) {
            HiLog.info(LABEL, "else MATCH_CONTENT = ");
            if (popupWindowHeight != 0) {
                return popupWindowHeight;
            } else {
                return popupWindowMaxHeight;
            }
        } else if (popupWindowHeight == ComponentContainer.LayoutConfig.MATCH_PARENT) {
            HiLog.info(LABEL, "else MATCH_PARENT = ");
            if (popupWindowHeight > 0) {
                return popupWindowHeight;
            } else {
                return popupWindowMaxHeight;
            }
        }
        return ComponentContainer.LayoutConfig.MATCH_CONTENT;
    }

    /**
     * Get the {@link PopupDialog}.
     *
     * @return The {@link PopupDialog} that is displayed when the view has been clicked.
     */
    public PopupDialog getPopupWindow() {
        return popupWindow;
    }

    /**
     * Get the {@link ListContainer} that is used in the dropdown menu
     *
     * @return the ListView shown in the PopupDialog.
     */
    public ListContainer getListView() {
        return listView;
    }

    /**
     * Interface definition for a callback to be invoked when an item in this view has been selected.
     *
     * @param <T> Adapter item type
     * @since 2021-07-30
     */
    public interface OnItemSelectedListener<T> {
        /**
         * <p>Callback method to be invoked when an item in this view has been selected. This callback is invoked only when
         * the newly selected position is different from the previously selected position or if there was no selected
         * item.</p>
         *
         * @param view The {@link MaterialSpinner} view
         * @param position The position of the view in the adapter
         * @param id The row id of the item that is selected
         * @param item The selected item
         */
        void onItemSelected(MaterialSpinner view, int position, long id, T item);
    }

    /**
     * Interface definition for a callback to be invoked when the dropdown is dismissed and no item was selected.
     *
     * @since 2021-07-30
     */
    public interface OnNothingSelectedListener {
        /**
         * Callback method to be invoked when the {@link PopupDialog} is dismissed and no item was selected.
         *
         * @param spinner the {@link MaterialSpinner}
         */
        void onNothingSelected(MaterialSpinner spinner);
    }
}